/*-------------------------------------------------------------------------
 *
 * auth-sasl.c
 *	  处理通过SASL进行身份验证的例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/libpq/auth-sasl.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "libpq/auth.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "libpq/sasl.h"

/*
 * 最大接受的SASL消息大小。
 *
 * 服务器或libpq生成的消息远小于此，但有一些余地。
 */
#define PG_MAX_SASL_MESSAGE_LENGTH	1024

/*
 * 使用特定机制实现与libpq客户端执行SASL交换。
 *
 * shadow_pass是指向已认证角色的存储秘密的可选指针，
 * 来自pg_authid.rolpassword。对于使用隐藏密码的机制，
 * 这里的NULL指针意味着未能找到该角色的条目（或用户不存在），
 * 机制应当使身份验证交换失败。
 *
 * 机制必须小心不要向客户端泄露用户条目不存在的信息；
 * 理想情况下，外部失败模式与密码错误的失败模式相同。
 * 机制可以使用logdetail输出参数在内部区分失败情况
 * 并帮助服务器管理员进行调试。
 *
 * 机制并不要求使用shadow条目，甚至完全不使用密码系统；
 * 对于这些情况，可以忽略shadow_pass，调用者应该只传递NULL。
 */
int CheckSASLAuth(const pg_be_sasl_mech *fc_mech, Port *fc_port, char *fc_shadow_pass,
			  const char **fc_logdetail)
{
	StringInfoData fc_sasl_mechs;
	int			fc_mtype;
	StringInfoData fc_buf;
	void	   *fc_opaq = NULL;
	char	   *fc_output = NULL;
	int			fc_outputlen = 0;
	const char *fc_input;
	int			fc_inputlen;
	int			fc_result;
	bool		fc_initial;

	/*
	 * 将SASL身份验证请求发送给用户。它包含支持的身份验证机制列表。
	 */
	initStringInfo(&fc_sasl_mechs);

	fc_mech->get_mechanisms(fc_port, &fc_sasl_mechs);
	/* 再放一个'\0'以标记列表结束。 */
	appendStringInfoChar(&fc_sasl_mechs, '\0');

	sendAuthRequest(fc_port, AUTH_REQ_SASL, fc_sasl_mechs.data, fc_sasl_mechs.len);
	pfree(fc_sasl_mechs.data);

	/*
	 * 循环进行SASL消息交换。此交换可以由多个消息组成，
	 * 在两个方向上发送。第一条消息始终来自客户端。
	 * 所有从客户端到服务器的消息都是密码包（类型'p'）。
	 */
	fc_initial = true;
	do
	{
		pq_startmsgread();
		fc_mtype = pq_getbyte();
		if (fc_mtype != 'p')
		{
			/* 仅在客户端未断开连接时记录错误。 */
			if (fc_mtype != EOF)
			{
				ereport(ERROR,
						(errcode(ERRCODE_PROTOCOL_VIOLATION),
						 errmsg("expected SASL response, got message type %d",
								fc_mtype)));
			}
			else
				return STATUS_EOF;
		}

		/* 获取实际的SASL消息 */
		initStringInfo(&fc_buf);
		if (pq_getmessage(&fc_buf, PG_MAX_SASL_MESSAGE_LENGTH))
		{
			/* EOF - pq_getmessage已记录错误 */
			pfree(fc_buf.data);
			return STATUS_ERROR;
		}

		elog(DEBUG4, "processing received SASL response of length %d", fc_buf.len);

		/*
		 * 第一个SASLInitialResponse消息与其他消息不同。
		 * 它指示客户端选择了哪个SASL机制，并包含
		 * 可选的初始客户端响应有效负载。随后的
		 * SASLResponse消息仅包含SASL有效负载。
		 */
		if (fc_initial)
		{
			const char *fc_selected_mech;

			fc_selected_mech = pq_getmsgrawstring(&fc_buf);

			/*
			 * 初始化消息交换的状态跟踪器。
			 *
			 * 如果用户不存在，或者没有有效密码，或者
			 * 密码已过期，我们仍然进行SASL
			 * 身份验证，但告诉身份验证方法该
			 * 身份验证是“注定要失败的”。也就是说，不管
			 * 如何，它都会失败。
			 *
			 * 这是因为我们不想向攻击者透露哪些
			 * 用户名是有效的，以及哪些用户有有效密码。
			 */
			fc_opaq = fc_mech->init(fc_port, fc_selected_mech, fc_shadow_pass);

			fc_inputlen = pq_getmsgint(&fc_buf, 4);
			if (fc_inputlen == -1)
				fc_input = NULL;
			else
				fc_input = pq_getmsgbytes(&fc_buf, fc_inputlen);

			fc_initial = false;
		}
		else
		{
			fc_inputlen = fc_buf.len;
			fc_input = pq_getmsgbytes(&fc_buf, fc_buf.len);
		}
		pq_getmsgend(&fc_buf);

		/*
		 * StringInfo保证响应后面有一个\0字节。
		 */
		Assert(fc_input == NULL || fc_input[fc_inputlen] == '\0');

		/*
		 * 将传入消息交给机制实现。
		 */
		fc_result = fc_mech->exchange(fc_opaq, fc_input, fc_inputlen,
								&fc_output, &fc_outputlen,
								fc_logdetail);

		/* 输入缓冲区不再使用 */
		pfree(fc_buf.data);

		if (fc_output)
		{
			/*
			 * SASL禁止带有一些输出的PG_SASL_EXCHANGE_FAILURE。
			 * 确保这里使用的机制得到了正确的处理。
			 */
			if (fc_result == PG_SASL_EXCHANGE_FAILURE)
				elog(ERROR, "output message found after SASL exchange failure");

			/*
			 * 协商生成的数据将发送给客户端。
			 */
			elog(DEBUG4, "sending SASL challenge of length %d", fc_outputlen);

			if (fc_result == PG_SASL_EXCHANGE_SUCCESS)
				sendAuthRequest(fc_port, AUTH_REQ_SASL_FIN, fc_output, fc_outputlen);
			else
				sendAuthRequest(fc_port, AUTH_REQ_SASL_CONT, fc_output, fc_outputlen);

			pfree(fc_output);
		}
	} while (fc_result == PG_SASL_EXCHANGE_CONTINUE);

	/* 哎呀，发生了一些错误 */
	if (fc_result != PG_SASL_EXCHANGE_SUCCESS)
	{
		return STATUS_ERROR;
	}

	return STATUS_OK;
}
